从这个章节开始,我们将详细讲解CC2640R2F BLE5.0的应用程序框架,在之前我们希望已经按照我们学习线路图储备了CC2640R2F平台的软硬件架构知识。明白应用工程区分App和Stack工程管理。这里我们主要是讲解基于TI-RTOS的App应用程序框架。
这里介绍以 simple_peripheral Demo应用程序部分,包括以下内容:
注意: GAPRoleTask也是工程的一部分,但是我们将它放在协议栈部分进行讨论,其功能与协议栈密切相关。
main
函数包含在IDE Startup 文件夹的资源文件main.c中。作为程序的入口,其主要完成 全局中断禁止、外设驱动初始化、电源管理,TI-RTOS任务创建或构造,启用SYS / BIOS内核调度时完成全局中断使能。main函数不返回,将在整个项目生命周期内保留其资源;
基本main.c功能。
int main()
{
/* Register Application callback to trap asserts raised in the Stack */
RegisterAssertCback(AssertHandler);
PIN_init(BoardGpioInitTable);
#ifndef POWER_SAVING
/* Set constraints for Standby, powerdown and idle mode */
Power_setConstraint(PowerCC26XX_SB_DISALLOW);
Power_setConstraint(PowerCC26XX_IDLE_PD_DISALLOW);
#endif // POWER_SAVING
/* Initialize ICall module */
ICall_init();
/* Start tasks of external images - Priority 5 */
ICall_createRemoteTasks();
/* Kick off profile - Priority 3 */
GAPRole_createTask();
SimpleBLEPeripheral_createTask();
/* enable interrupts and start SYS/BIOS */
BIOS_start();
return 0;
}
注意:以上代码ICALL消息模块必须由ICALL_init()完成初始化,并且通过ICall_createRemoteTasks()完成协议栈任务创建 。
在软件架构章节我们讲解了由于历史兼容原因将整个应用工程区分App和Stack两个工程管理,正是基于这样的两个工程设计,App和Stack之间的通信就需要重新考虑了,因为我们没有办法像常规API调用和全局变量方式完成消息传递。TI引入了ICALL消息机制完成App和Stack独立工程管理的彼此通信。以下我们着重理解原理和代码实现,因其占了我们程序很大部分。
Indirect Call Framework (ICall 消息框架)基于TI-RTOS提供服务(例如,同步线程、消息、事件)完成BLE协议栈和应用程序在两个工程的消息交互,保证了应用程序和协议栈在统一的TI-RTOS环境中完成高效运行、相互通信和资源共享。
ICall架构和的核心组件是其消息调度(dispatcher),其帮助程序在两个镜像/工程中完成BLE5-Stack协议栈以及库配置中的应用程序交互。尽管大多数ICall交互在BLE5-Stack API(例如GAP,HCI等)中已经被抽象化(已经被封装为消息原语函数),但是我们必须理解其在BLE-Stack和多线程RTOS环境中正常运行的基础架构。
ICALL源码在应用工程(例如我们这里的simple_peripheral)的 ICALL BLE/ICALL 文件夹路径。
如上图所示,ICALL实现包含一个客户端实体(例如,我们的应用程序)和一个服务器实体(即这里的BLE5.0协议栈)之间的通信。
注意 :正确区分ICALL框架的CS架构和GATT服务器和客户端的结构,后者主要由BLE协议栈所定义实现。
TI之所以这样设计,前面我们已经讲到:
ICall 作为 BLE5协议栈API的服务接口。当我们需要调用一些协议栈接口的时候,ICall模块会自动将命令分发(即调度)到BLE5协议栈,并将消息从BLE5协议栈结果回传到应用程序。
因为ICall消息模块本身就作为是应用程序工程的一部分,应用任务可以通过函数调用方式直接访问ICall。由于BLE协议栈任务总是以最高优先级执行,但是应用程序在没有数据返回时缺处于阻塞状态,必然有些API在会在协议栈任务立即响应,应用任务只有在消息通过ICALL分发时才能唤醒处理。另外一些API却只有等待应用任务通过ICAlL(事件更新)异步返回结果。
ICALl包含一系列的原语服务都被抽象成基于RTOS相关的函数接口。由于共享资源和线程之间的通信,应用程序必须使用以下ICALL原语服务功能:
ICall同协议栈的消息传递和线程同步都是是基于TI-RTOS多线程。
ICall两个任务的消息传递发生在通过消息队列发送一个阻塞消息。发送方动态分分配一段内存,将消息的内容写入内存,然后将这段内存发送(即排队)到接收线程,然后再使用事件标志。接受任务在收到事件标志后唤醒,复制内存消息,并且处理,之后再并将这段内存块释放。
协议栈使用ICall通知和发送消息到应用程序。ICall传递这些服务消息,应用程序任务接收它然后处理。
ICall为应用提供了全局堆管理的API用以动态内存分配。其堆的大小通过宏HEAPMGR_SIZE
配置。有关管理动态内存的更多详细信息,请参阅动态内存分配。BLE 协议栈的ICall使用此堆管理进行所有消息相关的内存管理,同样我们建议应用程序也使用这些ICall API来分配动态内存。
要实例化和初始化ICall服务,应用程序必须在启动TI-RTOS内核调度程序之前调用main()中的代码片段中的函数:
使用ICall的必需代码。
/* 初始化ICall模块 */
ICall _init ();
/* 启动协议栈任务 - 优先级 */
ICall _createRemoteTasks ();
调用ICall_init()初始化ICALL原语服务(例如,堆管理)和框架,调用 ICall _createRemoteTasks()创建但不启动BLE5-Stack任务。在使用ICall协议服务之前,服务器和客户端分别完成登记和注册。服务端在编译的时候就需要登记一个服务。登记函数使用一个全局的唯一标识符区分每个服务,也是作为后面通信地址。例如,BLE协议使用 ICALL_SERVICE_CLASS_BLE
做些蓝牙协议栈ICALL的消息交互的标识。
对于服务端的登记,包含在osal_icall_ble.c
文件
/ *ICALL服务端登记* /
ICall _enrollService (ICALL _SERVICE_CLASS_BLE ,NULL ,&entity ,&syncHandle );
客户端在ICALL调度程序发送和/或接收消息之前需要注册。
对于使用BLE5API的客户端(例如应用程序任务),客户端必须首先向ICall注册其任务 。该注册通常发生在应用程序的任务初始化功能中。下面的代码片段是 simple_peripheral_init
simple_peripheral.c中注册。
//ICALL客户端注册
ICall _registerApp (&selfEntity ,&syncEvent );
完成客户端注册前,需要传入结构体变量selfEntity
和syncEvent
,其值在函数返回时被初始化,以后服务端通过这两个变量来进行消息传递。syncEvent
参数表示事件标识,selfEntity
表示处理消息的目的任务,也是客户端实体以后通信的源地址,每个注册ICALL的客户端都需要使用唯一的syncEvent
和selfEntity
。
注意: 在客户端注册ICALL服务之前,在
ICallBLEApi.c
文件里面定义的ICALL相关API都不能调用的。
ICALL使用TI-RTOS的事件用以线程同步而不是信号量。
为了让客户端或服务端在收到消息前都保持阻塞状态, ICall提供以下阻塞型API保持任务阻塞状态直到关联的信号量被Post:
UInt Event_pend(Event_Handle handle, UInt andMask, UInt orMask, UInt32 timeout);
handle
表示是构造的Event_Handle的句柄(标识符)。andMask
并 orMask
为用户选择要阻塞/挂起的事件标志。timeout
是以毫秒为单位的超时周期。如果在此超时时间范围内后尚未返回,该函数将返回。
Event_pend
函数表示阻塞当前任务等待某一事件标志位发生。
一共有32个事件标志位(int类型),这些标识位可以根据其特定用途进行定义。不过需要注意的是,ICall消息队列已经保留了特定事件标志位。
与TI-RTOS线程关联的事件由Event_post所需标志调用时发出/发布
Event_post
用以客户端/服务端将某个任务阻塞任务激活成运行状态,并且通过发送的对应的标志位执行指定动作。
void Event_post(Event_Handle handle,UInt eventMask);
以上的事件句柄handle
在服务端ICall _enrollService()和ICall _registerApp()调用后获得。
危险: 不要从协议栈回调中调用ICall函数。此操作可能导致ICall中止(使用ICall_abort())并中断系统。
下图显示了一个通过 ICall框架从应用程序发送到BLE5-Stack的示例命令,并将相应的返回值传回给应用程序。
ICall _init()完成初始化 ICall模块实例,而 ICall_createRemoteTasks()使用已知地址的入口函数为协议栈创建任务。
初始化后的iCall,App作为客户端通过ICall_registerApp()完成注册。
在SYS / BIOS调度程序启动并且应用程序任务运行之后,应用程序发送一个ble_dispatch_JT.c 如GAP_GetParamValue()中定义的协议命令。
协议命令不在应用程序的线程中执行,而是封装在ICall消息中,并通过ICall框架发送到BLE5-Stack任务。该命令被发送到ICALL调度程序,它在BLE5-Stack上下文中调度和执行。同时应用程序线程阻塞,直到接收到相应的命令状态消息。BLE5-Stack完成执行命令,然后通过ICall将命令状态消息响应发送回应用程序线程。这种交换的示例图如下所示。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_createRemoteTasks Line 519
void ICall_createRemoteTasks(void {
size_t i;
UInt keytask;
/* Cheap locking mechanism to lock tasks
* which may attempt to access the service call dispatcher
* till all services are registered.
*/
keytask = Task_disable();
for (i = 0; i < ICALL_REMOTE_THREAD_COUNT; i++) {
Task_Params params;
Task_Handle task;
ICall_CSState key;
Task_Params_init(¶ms);
params.priority = ICall_threadPriorities[i];
params.stackSize = ICall_threadStackSizes[i];
params.arg0 = (UArg) icall_threadEntries[i];
params.arg1 = (UArg) ICall_getInitParams(i);
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_taskEntry Line 482
static Void ICall_taskEntry(UArg arg0, UArg arg1) {
ICall_RemoteTaskEntry entryfn = (ICall_RemoteTaskEntry) arg0;
entryfn(&ICall_taskEntryFuncs, (void *) arg1);
}
FlashROM_StackLirary
这里直接链接的编译在静态库中的startup_entry
函数。//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_createRemoteTasks Line 540
params.arg0 = (UArg) icall_threadEntries[i];
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c Line228
static const ICall_RemoteTaskEntry icall_threadEntries[] = ICALL_ADDR_MAPS;
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_addrs.h Line99
#ifdef STACK_LIBRARY
extern void startup_entry( const ICall_RemoteTaskArg *arg0, void *arg1 );
//extern ICall_RemoteTaskEntry startup_entry;
#define ICALL_ADDR_MAPS \
{ \
(ICall_RemoteTaskEntry) (startup_entry) \
}
#else /* ! STACK_LIBRARY */
#define ICALL_ADDR_MAPS \
{ \
(ICall_RemoteTaskEntry) (ICALL_STACK0_ADDR) \
}
#endif /* STACK_LIBRARY */
而对于FlashROM
编译选项这里的arg0
为一个绝对地址,该绝对地址通过边界工具frontier.exe
计算协议栈工程的编译生成的存储映射文件*.map所得。并将计算结果保存在应用工程的iar_boundary.xcl
文件。
//Project->Options(Alt+F7)->Build Actions
"$TOOLS_BLE$/frontier/frontier.exe" iar "$PROJ_DIR$/$CONFIG_NAME$/List/simple_peripheral_cc2640r2lp_stack.map" "$PROJ_DIR$/../config/iar_boundary.bdef" "$PROJ_DIR$/../config/iar_boundary.xcl"
/*
** Stack Frontier Generator 1.1.0 (2017-06-28 14:28:12.107000)
**
** WARNING - Auto-generated file. Modifications could be lost!
*/
--config_def ICALL_RAM0_START=0x20003fe8
--config_def ICALL_STACK0_START=0x00016a00
--config_def ICALL_STACK0_ADDR=0x00016a01
Okay,大概说下结论,对于协议栈工程是作为App工程的一个任务启动的,该任务创建区分协议栈是编译成独立镜像,还是静态链接库方式,对于前置通过工具自动计算首地址作为协议栈的任务的入口地址,对于后者直接链接协议栈的入口地址作为协议栈任务的任务实体。
ICALL_SERVICE_CLASS_BLE_MSG
作为服务端的源地址。//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\stack\osal_icall_ble.c stack_main Line 229
if (ICall_enrollService(ICALL_SERVICE_CLASS_BLE_MSG,
(ICall_ServiceFunc) osal_service_entry,
&osal_entity,
&osal_syncHandle) != ICALL_ERRNO_SUCCESS)
{
SimpleBLEPeripheral_taskFxn
完成该任务的Icall客户端注册,并且返回selfEntity
作为改客户端Icall通信的源地址。//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c SimpleBLEPeripheral_init Line 452
ICall_registerApp(&selfEntity, &syncEvent);
icall_directAPI
函数中。//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\examples\rtos\CC2640R2_LAUNCHXL\ble5stack\simple_peripheral\src\app\simple_peripheral.c SimpleBLEPeripheral_init Line 497
// Setup the GAP
GAP_SetParamValue(TGAP_CONN_PAUSE_PERIPHERAL, DEFAULT_CONN_PAUSE_PERIPHERAL);
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\inc\icall_ble_api.h GAP_SetParamValue Line 313
#define GAP_SetParamValue(...) (icall_directAPI(ICALL_SERVICE_CLASS_BLE, (uint32_t) IDX_GAP_SetParamValue , ##__VA_ARGS__))
而对于icall_directAPI
,将所有变参封装到一个的消息结构体,并且调用ICall_sendServiceMsg
->ICall_send
。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c icall_directAPI Line3819
icallLiteMsg_t liteMsg;
liteMsg.hdr.len = sizeof(icallLiteMsg_t);
liteMsg.hdr.next = NULL;
liteMsg.hdr.dest_id = ICALL_UNDEF_DEST_ID;
liteMsg.msg.directAPI = id;
liteMsg.msg.pointerStack = (uint32_t*)(*((uint32_t*)(&argp)));
ICall_sendServiceMsg(ICall_getEntityId(), service,
ICALL_MSG_FORMAT_DIRECT_API_ID, &(liteMsg.msg));
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c ICall_sendServiceMsg Line 2419
return (ICall_send(src, dstentity, format, msg));
最终ICall_send
是直接将消息放入了目的Icall消息实体的消息队列中,并且触发事件,通知该任务唤醒解析处理消息。
//C:\ti\simplelink_cc2640r2_sdk_1_35_00_33\source\ti\ble5stack\icall\src\icall.c Icall_Send Line 2661
ICall_msgEnqueue(&ICall_entities[dest].task->queue, msg);
ICALL_SYNC_HANDLE_POST(ICall_entities[dest].task->syncHandle);
对于协议栈任务,因为分布在另外一个工程,所以我们没有向原来那样直接加断点调试。上面我们详细讲解了协议栈是如何作为一个任务在应用工程中启动的,所以找到协议任务入口地址就是我们调试协议栈任务的关键。
在调试协议栈任务之前,我们建议将分别设置协议栈和应用工程优化等级Project->Opitons->C/C++ Compiler Optimizations->Level->None 为无,这对我们正常调试协议栈任务至关重要。
上面我们已经详细讲解过,对于FlashROM
编译选项,协议栈任务入口地址在iar_boundary.xcl
地址给出,对于FlashROM_StackLibrary
编译选项,我们可以通过编译生成的*.map文件查找startup_entry
符号从而找到入口地址。
拿到协议栈任务的人口地址后,我们就可以在汇编窗口View->Disassembly 直接输入该地址,然后加上断点,等待协议栈任务创建后运行,从而跳转到协议栈工程进行调试。
当然如果我们不知道协议栈任务的入口地址,直接通过创建协议栈任务的任务实体入口,直接跳转到ICall_taskEntry
->entryfn
然后按F11进入协议栈任务调试。
注意:这里F11 Step Into可能会优化选项跳转失败,所以建议关闭优化选项,或者直接到聚焦到汇编窗口 BX R2 F11 跳转。
简单外设任务作为应用程序任务是系统中最低优先级的任务。该任务的代码是在Application IDE文件夹中的simple_peripheral 文件夹simple_peripheral.c
中。
TI-RTOS概述详细介绍如何了任务构建。构建任务并启动SYS / BIOS内核调度程序后,构造过程中传递的函数会在任务准备就绪时运行(例如 SimpleBLEPeripheral_taskFxn)。任务实体函数运行前这里会先运行任务初始化。
simple_peripheral任务函数伪代码
static void SimpleBLEPeripheral_taskFxn (UArg a0 , UArg a1 ) {
//初始化应用程序
SimpleBLEPeripheral_init ();
// Application main loop
for (;;) {
}
}
这个初始化函数(simple_peripheral_init)为任务配置了几个服务,并设置了几个硬件和软件配置设置和参数。以下列表包含一些常见示例:
注意:在应用程序初始化函数中,调用任何协议栈API之前,必须调用ICall _registerApp()完成注册。
simple_peripheral实现以事件驱动的任务功能。任务函数进入一个无限循环,使其不间断地为一个独立的任务处理,并且始终不会运行到完成。在这个无限循环中,任务保持阻塞并等待,直到事件标志更新后进入事件处理函数。:
ICall任务保持阻塞并等待,直到发信号通知进行处理。
// Waits for an event to be posted associated with the calling thread.
// Note that an event associated with a thread is posted when a
// message is queued to the message receive queue of the thread
events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
ICALL_TIMEOUT_FOREVER);
当事件发生并被处理后,任务又等待事件标志并且保保持阻塞状态,直到有另一个事件发生。
当BLE5-Stack通过ICAll消息模块在应用程序任务中设置事件时,任务事件发生。一个比较好的例子就是调用HCI_EXT_ConnEventNoticeCmd()来指示协议栈connection event
结束。表示该事件结束的任务事件也将显示在simple_peripheral的任务函数中:
SBP任务检查任务事件。
if (events)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
if (ICall_fetchServiceMsg(&src, &dest,
(void **)&pMsg) == ICALL_ERRNO_SUCCESS)
{
uint8 safeToDealloc = TRUE;
if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
{
ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
// Check for BLE stack events first
if (pEvt->signature == 0xffff)
{
if (pEvt->event_flag & SBP_CONN_EVT_END_EVT)
{
// Try to retransmit pending ATT Response (if any)
SimpleBLEPeripheral_sendAttRsp();
}
}
else
{
// Process inter-task message
safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
}
}
if (pMsg && safeToDealloc)
{
ICall_freeMsg(pMsg);
}
}
// Additional Event Processing
}
注意:在当前代码中,如果事件来自BLE5-Stack ,则pEvt->signature总是等于0xFFFF。
当为一个事件选择一个事件值的时候,该值对于给定的任务必须是唯一的,并且必须是2的幂(只有1bit被设置为1)。因为pEvt->event
变量被初始化为 uint16_t
类型,也就是最多运行允许16个事件。有一个不能使用的事件值是已经用于BLE5-Stack OSAL全局事件(bcomdef.h中所述)的事件值:
清单49. BLE OSAL事件在bcomdef.h中定义。
/************************************************************
* BLE OSAL GAP GLOBAL Events
*/
#define GAP_EVENT_SIGN_COUNTER_CHANGED 0x4000 //!< The device level sign counter changed
注意:这些任务间事件是与
请求和处理协议栈事件
中提到的内部事件不同的事件集。
这些消息通过ICall从一个任务(如BLE5-Stack Service/ICALL 服务端)传递给应用程序任务(ICALL 客户端)。
正如以下情形:
Task事件来自simple_peripheral的主要任务循环的一个例子。
所有BLE5-Stack 1.00.00项目使用TI-RTOS事件模块获取ICall堆栈消息事件。ICall线程同步中描述了使用情况 ,有关事件模块的更多文档,请参见“ TI RTOS内核用户指南”。
使用Util_enqueueMsg() 函数将应用程序消息放入队列以先进先出的顺序进行处理。当UTIL_QUEUE_EVENT_ID发布事件时,应用程序应该从消息队列取出消息处理后并释放。
下面的代码片段显示了simple_peripheral如何处理应用程序消息。
#define SBP_QUEUE_EVT UTIL_QUEUE_EVENT_ID // Event_Id_30
// If TI-RTOS queue is not empty, process app message.
if (events & SBP_QUEUE_EVT)
{
while (!Queue_empty(appMsgQueue))
{
sbpEvt_t *pMsg = (sbpEvt_t *)Util_dequeueMsg(appMsgQueue);
if (pMsg)
{
// Process message.
SimpleBLEPeripheral_processAppMsg(pMsg);
// Free the space from the message.
ICall_free(pMsg);
}
}
}
某些API可以选择在BLE5-Stack中发生特定事件时通知应用程序。启用此类事件通知的API将包含一个taskEvent
参数。taskEvent
对于给定的ICall-aware任务,该值必须是唯一的 。应用程序可以通过检查是否taskEvent
包含在数据结构ICall_Stack_Event
的uint16_t event_flag
变量中来处理所请求的协议栈事件。
注意 :在event_flag不与的TI-RTOS事件模块事件混淆。
下面的代码片段显示了simple_peripheral如何请求协议栈事件标志。
连接间隔结束时请求通知的应用程序。
// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT 0x0001
static uint8_t SimpleBLEPeripheral_processGATTMsg(gattMsgEvent_t *pMsg)
{
// See if GATT server was unable to transmit an ATT response
if (pMsg->hdr.status == blePending)
{
// No HCI buffer was available. Let's try to retransmit the response
// on the next connection event.
if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity,
SBP_HCI_CONN_EVT_END_EVT) == SUCCESS)
{
// First free any pending response
SimpleBLEPeripheral_freeAttRsp(FAILURE);
// Hold on to the response message for retransmission
pAttRsp = pMsg;
//...
}
//...
}
//...
}
下面的代码片段显示了simple_peripheral如何处理协议栈事件标志。
处理请求BLE5-Stack事件
// Application specific event ID for HCI Connection Event End Events
#define SBP_HCI_CONN_EVT_END_EVT 0x0001
static void SimpleBLEPeripheral_taskFxn(UArg a0, UArg a1)
{
// Application main loop
for (;;)
{
uint32_t events;
// Waits for an event to be posted associated with the calling thread.
// Note that an event associated with a thread is posted when a
// message is queued to the message receive queue of the thread
events = Event_pend(syncEvent, Event_Id_NONE, SBP_ALL_EVENTS,
ICALL_TIMEOUT_FOREVER);
if (events)
{
ICall_EntityID dest;
ICall_ServiceEnum src;
ICall_HciExtEvt *pMsg = NULL;
if (ICall_fetchServiceMsg(&src, &dest,
(void **)&pMsg) == ICALL_ERRNO_SUCCESS)
{
uint8 safeToDealloc = TRUE;
if ((src == ICALL_SERVICE_CLASS_BLE) && (dest == selfEntity))
{
ICall_Stack_Event *pEvt = (ICall_Stack_Event *)pMsg;
// Check for BLE stack events first
if (pEvt->signature == 0xffff)
{
if (pEvt->event_flag & SBP_HCI_CONN_EVT_END_EVT)
{
// Try to retransmit pending ATT Response (if any)
SimpleBLEPeripheral_sendAttRsp();
}
}
else
{
// Process inter-task message
safeToDealloc = SimpleBLEPeripheral_processStackMsg((ICall_Hdr *)pMsg);
}
}
}
}
}
}
应用程序代码还包括对协议栈层,配置文件和TI-RTOS模块的各种回调函数。为了确保线程的安全性,回调要尽量做最少事情,大部分的处理应该发生在应用程序上下文中。每个回调定义了两个函数。一个是回调本身,另外一个就是在任务环境中被处理回调事件函数。回调不直接处理,通过事件方式到任务中。可以参考 GAPRole 状态改变回调,其用以处理GAPRole状态变化。
危险:在回调函数中执行阻塞TI-RTOS函数调用或协议栈API是很危险的,这样的函数调用可能导致中止或未定义的行为。
simple_peripheral状态变化回调。
static void SimpleBLEPeripheral_stateChangeCB (gaprole_States_t newState ) {
SimpleBLEPeripheral_enqueueMsg (SBP_STATE_CHANGE_EVT , newState );
}
上面的代码片段显示了通过SimpleBLEPeripheral_gapRoleCBs
和GAPRole_StartDevice()
。回调只是在队列中放置一个消息来通知应用程序唤醒。一旦回调返回其父任务进入休眠状态。应用程序唤醒从消息队列取出消息处理同时调用以下代码片段。
simple_peripheral任务环境中的状态变化事件处理。
static void SimpleBLEPeripheral_processStateChangeEvt (gaprole_States_t newState ) {
// ...
}
文章所有代码、工具、文档开源。加入我们QQ群 591679055获取更多支持,共同研究CC2640R2F&BLE5.0。